Skip to content

feat(core): GSAP keyframe parsing, mutations, and API routes [1/6]#1167

Merged
miguel-heygen merged 1 commit into
mainfrom
feat/keyframes-1-core-parser
Jun 5, 2026
Merged

feat(core): GSAP keyframe parsing, mutations, and API routes [1/6]#1167
miguel-heygen merged 1 commit into
mainfrom
feat/keyframes-1-core-parser

Conversation

@miguel-heygen
Copy link
Copy Markdown
Collaborator

Summary

Parse native GSAP keyframes in 3 formats (percentage, object-array, simple-array). 5 mutation functions (add/remove/update keyframe, convert-to-keyframes, remove-all). Expand SUPPORTED_PROPS to 21. Studio API routes for all mutation types. 92 test cases.

Part 1 of 6 — core parser foundation. No UI changes.

Test plan

  • bun run --cwd packages/core test — all parser tests pass
  • Keyframe parsing: percentage, object-array, simple-array formats
  • Mutations: add, remove, update, convert, remove-all

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

Fallow audit report

Found 46 findings.

Duplication (33)
Severity Rule Location Description
minor fallow/code-duplication packages/core/src/parsers/gsapParser.stress.test.ts:296 Code clone group 1 (10 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.stress.test.ts:313 Code clone group 2 (13 lines, 5 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.stress.test.ts:499 Code clone group 2 (13 lines, 5 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:281 Code clone group 1 (10 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:296 Code clone group 2 (13 lines, 5 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:312 Code clone group 2 (13 lines, 5 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:346 Code clone group 2 (13 lines, 5 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1096 Code clone group 3 (14 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1139 Code clone group 3 (14 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1197 Code clone group 4 (16 lines, 3 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1209 Code clone group 5 (10 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1210 Code clone group 6 (6 lines, 3 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1216 Code clone group 7 (6 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1224 Code clone group 4 (16 lines, 3 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1239 Code clone group 5 (10 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1240 Code clone group 6 (6 lines, 3 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1248 Code clone group 7 (6 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1257 Code clone group 4 (16 lines, 3 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1271 Code clone group 6 (6 lines, 3 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1451 Code clone group 8 (8 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1470 Code clone group 8 (8 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:562 Code clone group 9 (7 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:660 Code clone group 9 (7 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:919 Code clone group 10 (8 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:1170 Code clone group 11 (15 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:1251 Code clone group 11 (15 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:1314 Code clone group 10 (8 lines, 2 instances)
minor fallow/code-duplication packages/core/src/studio-api/routes/files.ts:398 Code clone group 12 (5 lines, 2 instances)
minor fallow/code-duplication packages/core/src/studio-api/routes/files.ts:654 Code clone group 13 (5 lines, 2 instances)
minor fallow/code-duplication packages/core/src/studio-api/routes/files.ts:662 Code clone group 14 (5 lines, 2 instances)
minor fallow/code-duplication packages/core/src/studio-api/routes/files.ts:694 Code clone group 13 (5 lines, 2 instances)
minor fallow/code-duplication packages/core/src/studio-api/routes/files.ts:702 Code clone group 14 (5 lines, 2 instances)
minor fallow/code-duplication packages/core/src/studio-api/routes/render.ts:48 Code clone group 12 (5 lines, 2 instances)
Health (13)
Severity Rule Location Description
critical fallow/high-crap-score packages/core/src/parsers/gsapParser.ts:71 'resolveNode' has CRAP score 315.9 (threshold: 30.0, cyclomatic 36)
minor fallow/high-crap-score packages/core/src/parsers/gsapParser.ts:145 'selectorFromQueryCall' has CRAP score 49.5 (threshold: 30.0, cyclomatic 13)
minor fallow/high-crap-score packages/core/src/parsers/gsapParser.ts:223 'visitCallExpression' has CRAP score 43.1 (threshold: 30.0, cyclomatic 12)
minor fallow/high-crap-score packages/core/src/parsers/gsapParser.ts:282 'resolveTargetSelector' has CRAP score 43.1 (threshold: 30.0, cyclomatic 12)
minor fallow/high-crap-score packages/core/src/parsers/gsapParser.ts:311 'objectExpressionToRecord' has CRAP score 43.1 (threshold: 30.0, cyclomatic 12)
minor fallow/high-crap-score packages/core/src/parsers/gsapParser.ts:402 'visitCallExpression' has CRAP score 31.6 (threshold: 30.0, cyclomatic 10)
minor fallow/high-crap-score packages/core/src/parsers/gsapParser.ts:959 'buildTweenStatementCode' has CRAP score 31.6 (threshold: 30.0, cyclomatic 10)
major fallow/high-crap-score packages/core/src/parsers/gsapParser.ts:1285 'resolveConversionProps' has CRAP score 43.1 (threshold: 30.0, cyclomatic 12)
minor fallow/high-crap-score packages/core/src/parsers/gsapSerialize.ts:66 'lines' has CRAP score 43.1 (threshold: 30.0, cyclomatic 12)
minor fallow/high-crap-score packages/core/src/parsers/gsapSerialize.ts:222 '<arrow>' has CRAP score 37.1 (threshold: 30.0, cyclomatic 11)
minor fallow/high-crap-score packages/core/src/parsers/gsapSerialize.ts:274 '<arrow>' has CRAP score 43.1 (threshold: 30.0, cyclomatic 12)
critical fallow/high-crap-score packages/core/src/studio-api/routes/files.ts:434 '<arrow>' has CRAP score 160.0 (threshold: 30.0, cyclomatic 25)
critical fallow/high-crap-score packages/core/src/studio-api/routes/files.ts:605 '<arrow>' has CRAP score 238.6 (threshold: 30.0, cyclomatic 31)

Generated by fallow.

@miguel-heygen miguel-heygen force-pushed the feat/keyframes-1-core-parser branch from 20f6ede to 99f09cf Compare June 4, 2026 16:15
Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Foundational PR for the stack. The parser shape is the right altitude — three GSAP keyframe formats (percentage / object-array / simple-array) cleanly distinguished by AST inspection, mutations operate via AST round-trip (not string rewrite), and the auto-collapse-to-flat-tween behavior on <2 keyframes is a nice ergonomic. 92 test cases cover the important shapes.

Strengths confirmed:

  • CSS_IDENTITY map (opacity:1, scale:1, autoAlpha:1) for synthesizing implicit-from values when converting to() → keyframes is the right approach. Falling back to 0 for everything else is documented and reasonable.
  • removeKeyframeFromScript auto-collapses to flat tween when < 2 keyframes remain, preserving the last keyframe's properties. Good DX.
  • DROPPED_VAR_KEYS correctly removes keyframes from the drop set (now parsed) while keeping onComplete/onStart/etc. dropped. Callbacks remain out of scope.

Non-blocking nits:

  1. Object-array percentage collisions on small durationsparseObjectArrayKeyframes computes positions via Math.round((cumulative / totalDuration) * 100). Two adjacent keyframes with very small durations (e.g., 0.01s + 0.01s out of a 5s timeline) round to the same percentage. The mutation functions key on percentage exact-match (existingIdx !== -1) and silently replace. If a round-trip parse loses a keyframe, the next mutation can't address it. Worth either: (a) using higher-precision percentages internally (decimals), or (b) documenting the loss in the parse function's doc.

  2. Mutation functions only operate on percentage-formatfindKeyframesObjectNode matches ObjectExpression only. If a tween was parsed from object-array or simple-array format, addKeyframeToScript / updateKeyframeInScript / etc. silently return the input unchanged. Either: (a) document that mutations require percentage format, or (b) add a "normalize to percentage" pre-step that rewrites object-array/simple-array tweens before mutating. Caller can't currently introspect which format a tween uses.

  3. resolvedFromValues param name in convertToKeyframesInScript — for from() tweens, this is actually the to-state (the resolved current DOM state, since from() means "start FROM these values and animate TO current DOM"). The current name suggests "from values" universally. Suggest oppositeStateValues or a docstring example. The semantics are correct — just the naming is misleading.

  4. API route — no percentage range validation — the new add-keyframe / update-keyframe cases accept arbitrary percentage: number. Negative or > 100 values get serialized as e.g. "-50%" which the parser would happily round-trip but GSAP behavior is undefined. One-line guard: if (body.percentage < 0 || body.percentage > 100) return c.json({error: "percentage out of range"}, 400).

  5. Three fallow-ignore-next-line complexity directives in the new parse functions. Acceptable for irreducible parser branching across three formats; just flagging that they're load-bearing.

  6. Missing test cases — no test for: (a) mutations on object-array/simple-array format tweens (current behavior is silent no-op), (b) negative or >100 percentages, (c) tween with extras that survive a convertToKeyframesInScript round-trip.

Architecture solid. James gates stamps on my end. From a correctness perspective the parser + mutation primitives are ready.

Review by Jerrai (hyperframes specialist)

@miguel-heygen miguel-heygen merged commit e639a16 into main Jun 5, 2026
45 of 46 checks passed
@miguel-heygen miguel-heygen deleted the feat/keyframes-1-core-parser branch June 5, 2026 15:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants